iOS开发 - 利用Runtime优化归解档

Runtime 是 iOS 开发中的知名黑魔法,充分体现了 Objective - C 的动态运行时特性,Swift 是一门静态语言,这意味着代码编译时即确定了其实际调用的方法和类型,所以纯粹的 Swift 类和对象没有办法使用 Runtime,更不存在 Method swizzling,但是呢,Swift 是兼容 Objective - C 的,继承自 NSObject 的类,也就具有了运行时的消息机制。

纯粹的 Swift 类,也可以通过添加 @objc 修饰符, @objc 关键字只需要对那些不是继承自 NSObject 的类型进行,如果 class 是继承自 NSObject 的话,Swift 会默认自动为所有的非 private 的类和成员加上 @objc

@objc 修饰符主要用于需要暴露给 Objective-C 使用的任何地方 (包括类,属性和方法等) ,以支持其动态派发和运行时机制。

添加 @objc 修饰符并不意味着这个方法或者属性会变成动态派发,Swift 依然可能会将其优化为静态调用。如果需要施展一些像 Method swizzling 或者运行时再决定实现这样的 “黑魔法” 的时候,我们就必须用到 dynamic 修饰符了。 dynamic 修饰符会隐式的添加 @objc 进行修饰。

也就是说,需要 Swift 和 Objective 混合开发时,使用 @objc 就可以了,需要 Swift 完全实现运行时机制时,那么必须要添加 dynamic 修饰符。

下面我们通过优化归解档,来了解 Runtime 在 Swift 中的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class Person: NSObject, NSCoding {
var name = ""
var age = 0
init(name: String, age: Int) {
self.name = name
self.age = age
}
func encode(with aCoder: NSCoder) {
//属性的个数
var count: UInt32 = 0
//获取属性列表,inout属性的个数
let ivars = class_copyIvarList(Person.self, &count)
for i in 0 ..< count {
//取出属性
if let ivar = ivars?[Int(i)] {
//获取属性名
let key = String(utf8String: ivar_getName(ivar))
//使用KVC获取属性值,并归档
aCoder.encode(self.value(forKey: key!), forKey: key!)
}
}
//释放ivars
free(ivars)
}
required init?(coder aDecoder: NSCoder) {
super.init()
//属性的个数
var count: UInt32 = 0
//获取属性列表,inout属性的个数
let ivars = class_copyIvarList(Person.self, &count)
for i in 0 ..< count {
//取出属性
if let ivar = ivars?[Int(i)] {
//获取属性名
let key = String(utf8String: ivar_getName(ivar))
//解档
let value = aDecoder.decodeObject(forKey: key!)
//使用KVC传值
self.setValue(value, forKey: key!)
}
}
//释放ivars
free(ivars)
}
}

当我们使用归解档,属性很多或者结构很复杂时,会产生很多的重复代码,这时就可以通过 Runtime 来获取类的属性列表,属性名,属性数量等,再通过 KVC 完成取值和赋值,大大简化了这一步骤。

下面是使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@IBAction func save(_ sender: AnyObject) {
let max = Person(name: "Max", age: 100)
let min = Person(name: "Min", age: 0)
let result = NSKeyedArchiver.archiveRootObject([max, min], toFile: filePath!)
print("result:", result, "filePath:", filePath)
}
@IBAction func read(_ sender: AnyObject) {
let arr = NSKeyedUnarchiver.unarchiveObject(withFile: filePath!) as! [Person]
let max = arr.first
let min = arr.last
print(max?.name, min?.name)
}

Runtime 还有很多强大的方法,不仅仅是针对 Class,还有 Method、SEL、Protocol等,我们以后再讲。